{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"https://chengmo-dev1.bj.bcebos.com/page1.png\" alt=\"drawing\" width=\"1000\"/>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 前言 - 学习本项目你可以获得什么\n",
    "- 理论学习：了解AIAgent的基础知识\n",
    "- 上手实操：深入了解Agent中的FunctionCall运行流程\n",
    "- 上手实操：入门百度智能云千帆AppBuilder，在十分钟内打造一个个性化AIAgent\n",
    "- 上手实操：使用AppBuilder-SDK打造一个端云组件联动的进阶Agent"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. 项目背景\n",
    "\n",
    "### 1.1、 什么是AppBuilder\n",
    "[百度智能云千帆AppBuilder](https://appbuilder.cloud.baidu.com/)（以下简称AppBuilder）是基于大模型搭建AI原生应用的工作台，旨在降低AI原生应用的开发门槛，赋能开发者和企业快速实现应用搭建。\n",
    "\n",
    "平台提供了RAG（检索增强生成）、Agent（智能体）等应用框架，内置了文档问答、表格问答、多轮对话、生成创作等多种应用组件，还包括百度搜索和百度地图等特色组件，以及文本处理、图像处理和语音处理等传统AI组件，支持零代码、低代码、全代码三种开发方式，满足不同开发能力的开发者和企业的场景需求。\n",
    "\n",
    "### 1.2、 什么是AppBuilder-SDK\n",
    "\n",
    "[百度智能云千帆AppBuilder-SDK](https://github.com/baidubce/app-builder)(以下简称AB-SDK)，百度智能云千帆AppBuilder-SDK是百度智能云千帆AppBuilder面向AI原生应用开发者提供的一站式开发平台的客户端SDK。\n",
    "\n",
    "<img src=\"https://chengmo-dev1.bj.bcebos.com/page2.png\" alt=\"drawing\" width=\"1000\"/>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2. 项目介绍 - 通过ToolCall实现端云组件联动的Agent\n",
    "### 2.1、 什么是Agent\n",
    "\n",
    "AIAgent是能够感知环境，基于目标进行决策并执行动作的智能化应用。不同于传统人工智能应用（主要指以规则引擎、机器学习、深度学习等技术为核心）和RPA机器人，AIAgent能够基于目标和对现状能力的认知，在环境约束中，依赖特定资源和现有工具，找到行动规则并将行动拆解为必要的步骤，自主执行步骤，达成目标。\n",
    "\n",
    "AIAgent具备三个核心能力：独立思考、自主执行、持续迭代。\n",
    "- 独立思考是指AlAgent能够根据给定任务目标和约束条件，进行任务规划和问题拆解，形成执行步骤（即工作流）；\n",
    "- 自主执行是指AlAgent能够调取各类组件和工具，按照执行步骤依次执行，实现任务目标；\n",
    "- 持续选代是指AlAgent能够自动记录任务目标、工作流和执行结果，基于结果反馈，沉淀专家知识和案例。\n",
    "\n",
    "AICopilot、AIAgent、大模型等名词在各类文章上经常混淆，此处简要说明下三者的区别。大模型一般是指大模型技术，AlAgent和Al Copilot是基于大模型技术的智能化应用，AlAgent和AlCopilot在功能和场景上存在差别。\n",
    "\n",
    "自主性是AIAgent和AI Copilot之间最大的区别。AI Copilot是“副驾驶”，只是提供建议而非决策，AIAgent是“主驾驶”，需要真正做出决策并开展行动。\n",
    "\n",
    "<img src=\"https://chengmo-dev1.bj.bcebos.com/page3.png\" alt=\"drawing\" width=\"1000\"/>\n",
    "\n",
    "### 2.2、 什么是ToolCall\n",
    "\n",
    "解释该问题，需要了解以下的知识点：`Agent工具` -> `FunctionCall` - `ToolCall`\n",
    "\n",
    "AIAgent 有四大核心组件：记忆、规划、工具和执行。其中工具部分，与我们的开发关系最密切，在各类Agent开发平台/工具中，常被称为“组件”、\"插件\"、\"能力\"等.\n",
    "\n",
    "关于Agent的工具的定义与分类，如下图~\n",
    "\n",
    "<img src=\"https://chengmo-dev1.bj.bcebos.com/page4.png\" alt=\"drawing\" width=\"1000\"/>\n",
    "\n",
    "Agent使用工具的流程，一般称为`FunctionCall`，最早由OpenAI提出，并在[Assistant API](https://platform.openai.com/docs/assistants/overview)中广泛应用。\n",
    "\n",
    "\n",
    "ToolCall，则是AppBuilder平台提出的一种进阶的FunctionCall，本质与OpenAI的FunctionCall一致，但具有以下两个特点：\n",
    "\n",
    "- **端云组件联动**： Agent 调用工具时，可以同时调用云端和本地组件。\n",
    "\n",
    "- **组件类型泛化**： AppBuilder在未来会支持多种类型组件，已经超出了Function的含义，例如数据库、记忆库、工作流等等\n",
    "\n",
    "\n",
    "\n",
    "### 2.3、 什么是端云组件联动，要解决什么问题\n",
    "\n",
    "我们首先从工具的执行位置出发展开~ 在使用如AppBuilder / Coze 等平台开发Agent时，我们可以使用很多平台组件广场中，官方提供的组件，这里组件开箱即用，非常方便。\n",
    "\n",
    "<img src=\"https://chengmo-dev1.bj.bcebos.com/page5.png\" alt=\"drawing\" width=\"1000\"/>\n",
    "\n",
    "但是存在一个问题，基于平台云端组件开发的应用，无法调用内网/局域网/私域的知识与能力，也无法与本地的工具进行联动，限制了Agent的灵活性。\n",
    "\n",
    "我们在解决实际业务问题时，常遇到需要访问内网链接API或本地/硬件功能的FunctionCall需求，AppBuilder ToolCall可以解决这个问题：\n",
    "\n",
    "* 1、用户可注册一个本地运行的组件到已发布的应用\n",
    "* 2、由AppBuilder-Agent的云端思考模型进行规划和参数生成\n",
    "* 3、用户基于生成的参数调用本地组件，并再上传运行结果\n",
    "* 4、以此实现将本地组件能力嵌入到应用整体流程\n",
    "\n",
    "\n",
    "<img src=\"https://chengmo-dev1.bj.bcebos.com/page6.png\" alt=\"drawing\" width=\"1000\"/>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 3、ToolCall（FunctionCall）基础知识介绍\n",
    "\n",
    "### 3.1、Agent是如何调用Tool的\n",
    "\n",
    "我们可以将Agent的黑箱拆解为以下几个部分：\n",
    "1. Agent的背景信息\n",
    "2. Agent的输入信息\n",
    "3. Agent的思考过程\n",
    "4. Agent触发组件调用\n",
    "5. Agent基于组件输出反思总结\n",
    "\n",
    "#### Agent的背景信息包含以下几个部分\n",
    "- 角色定义描述（Prompt）：定义Agent的角色\n",
    "- 能力描述（Prompt）：定义Agent可以干什么\n",
    "- 工具描述（JsonSchema/Str）：将工具的输入和输出，按照规范，定义为一段字符串，作为最终大模型Prompt的一部分\n",
    "\n",
    "#### Agent的输入信息包含以下几个部分\n",
    "- 用户输入（Query/Prompt）：用户输入的文本\n",
    "- 对话相关的文件（File/Url）：与本地对话相关的文件路径\n",
    "\n",
    "#### Agent的思考过程\n",
    "AppBuilder-Agent会将背景信息与输入信息，拼接为最终的Prompt，然后调用大模型推理。\n",
    "\n",
    "Prompt的一个简单且直观的例子是：\n",
    "\n",
    "你是`{角色定义描述}`，你可以做以下事情：`{能力描述}`，你可以使用这些工具：`{工具描述-description}`，工具依赖的输入是：`{工具描述-paramters-properties-name}`，这些输入的格式分别是`{工具描述-paramters-properties-type}`。现在用户的问题是`{用户输入}`，与该问题相关的文件是`{对话相关的文件}`，请你解决用户的这个问题。\n",
    "\n",
    "#### Agent触发组件调用\n",
    "\n",
    "如果用户的query和组件能够解决的问题匹配，那么大模型就会尝试根据prompt里给出的工具的描述，从query中提炼出该次调用工具所需的参数，生成一个ToolCall命令，交给执行组件的模块去执行。\n",
    "\n",
    "例如，我们给出的组件能力是\"查找公司内指定人员的信息\"，函数的参数名为\"name\"。当用户输入\"查找张三的信息\"，大模型会从query中提炼出参数\"name=张三\"这个信息。\n",
    "\n",
    "<img src=\"https://chengmo-dev1.bj.bcebos.com/page7.png\" alt=\"drawing\" width=\"1000\"/>\n",
    "\n",
    "#### Agent基于组件输出反思总结\n",
    "\n",
    "组件运行模块执行组件后，会给出字符串形式的结果给到Agent，Agent会再次将结果拼接为Prompt，然后调用大模型推理。判断用户的需求是否已经解决。如果解决了，则经过一个对话模块，总结用户的需求，并生成一个对话记录。如果未解决，则继续调用大模型推理，尝试调用更多的工具，直到用户的需求被解决。\n",
    "\n",
    "### 3.2、开发者如何命令Agent调用本地Tool\n",
    "\n",
    "我们以AppBuilder-SDK中的AppBuilder-Client的基础代码为例，介绍开发者应该如何使用ToolCall功能\n",
    "\n",
    "\n",
    "```python\n",
    "import appbuilder\n",
    "\n",
    "# 实例化AppBuilderClient\n",
    "app_client = appbuilder.AppBuilderClient(app_id)\n",
    "conversation_id = app_client.create_conversation()\n",
    "\n",
    "# 第一次对话，输入原始的query 和 工具描述\n",
    "message_1 = app_client.run(\n",
    "    conversation_id=conversation_id,\n",
    "    query=\"请问张三同学的生日是哪天？\",\n",
    "    tools=tools\n",
    ")\n",
    "tool_call = message_1.content.events[-1].tool_calls[-1]\n",
    "tool_call_id = tool_call.id\n",
    "\n",
    "# 第二次对话，在本地执行组件后，上传组件的运行结果\n",
    "tool_call_result = \"张三同学的生日是2008年8月8日\"\n",
    "message_2 = app_client.run(\n",
    "    conversation_id=conversation_id,\n",
    "    tool_outputs=[{\n",
    "        \"tool_call_id\": tool_call_id,\n",
    "        \"output\": tool_call_result\n",
    "    }]\n",
    ")\n",
    "print(message_2.content)\n",
    "```\n",
    "\n",
    "其中`AppBuilderClient`的`run`方法是核心，我们展开该函数的定义和参数介绍：\n",
    "\n",
    "`AppBuilderClient().run() -> Message`\n",
    "\n",
    "```python\n",
    "def run(self, conversation_id: str,\n",
    "        query: str = \"\",\n",
    "        file_ids: list = [],\n",
    "        stream: bool = False,\n",
    "        tools: list[data_class.Tool] = None,\n",
    "        tool_outputs: list[data_class.ToolOutput] = None,\n",
    "        **kwargs\n",
    "        ) -> Message:\n",
    "    r\"\"\"\n",
    "        参数:\n",
    "            query (str: 必须): query内容\n",
    "            conversation_id (str, 必须): 唯一会话ID，如需开始新的会话，请使用self.create_conversation创建新的会话\n",
    "            file_ids(list[str], 可选):\n",
    "            stream (bool, 可选): 为True时，流式返回，需要将message.content.answer拼接起来才是完整的回答；为False时，对应非流式返回\n",
    "            tools(list[data_class.Tools], 可选): 一个Tools组成的列表，其中每个Tools对应一个工具的配置, 默认为None\n",
    "            tool_outputs(list[data_class.ToolOutput], 可选): 工具输出列表，格式为list[ToolOutput], ToolOutputd内容为本地的工具执行结果，以自然语言/json dump str描述，默认为None\n",
    "        返回: message (obj: `Message`): 对话结果.\n",
    "    \"\"\"\n",
    "    pass\n",
    "```\n",
    "\n",
    "\n",
    "| 参数名称        | 参数类型         | 是否必须 | 描述                                                         | 示例值            |\n",
    "| --------------- | ---------------- | -------- | ------------------------------------------------------------ | ----------------- |\n",
    "| conversation_id | String           | 是       | 会话ID                                                       |                   |\n",
    "| query           | String           | 否       | query问题内容                                                | \"今天天气怎么样?\" |\n",
    "| file_ids        | list[String]     | 否       | 对话可引用的文档ID                                           |                   |\n",
    "| stream          | Bool             | 否       | 为true时则流式返回，为false时则一次性返回所有内容, 推荐设为true，降低首token时延 | False             |\n",
    "| tools           | List[Tool]       | 否       | 一个列表，其中每个字典对应一个工具的配置                     |                   |\n",
    "| tools[0]        | Tool             | 否       | 工具配置                                                     |                   |\n",
    "| +type           | String           | 否       | 枚举：<br/>**file_retrieval**: 知识库检索工具能够理解文档内容，支持用户针对文档内容的问答。<br/>**code_interpreter**: 代码解释器, 代码解释器能够生成并执行代码，从而协助用户解决复杂问题，涵盖科学计算（包括普通数学计算题）、数据可视化、文件编辑处理（图片、PDF文档、视频、音频等）、文件格式转换（如WAV、MP3、text、SRT、PNG、jpg、MP4、GIF、MP3等）、数据分析&清洗&处理（文件以excel、csv格式为主）、机器学习&深度学习建模&自然语言处理等多个领域。<br/>**function**: 支持fucntion call模式调用工具 |                   |\n",
    "| +function       | Function         | 否       | Function工具描述<br/>仅当**type为**`**function**` 时需要且必须填写 |                   |\n",
    "| ++name          | String           | 否       | 函数名<br/>只允许数字、大小写字母和中划线和下划线，最大长度为64个字符。一次运行中唯一。 |                   |\n",
    "| ++description   | String           | 否       | 工具描述                                                     |                   |\n",
    "| ++parameters    | Dict             | 否       | 工具参数, json_schema格式                                    |                   |\n",
    "| tool_outputs    | List[ToolOutput] | 否       | 内容为本地的工具执行结果，以自然语言/json dump str描述       |                   |\n",
    "| tool_outputs[0] | ToolOutput       | 否       | 工具执行结果                                                 |                   |\n",
    "| +tool_call_id   | String           | 否       | 工具调用ID                                                   |                   |\n",
    "| +output         | String           | 否       | 工具输出                                                     |                   |\n",
    "\n",
    "`Tool`与`Function`是本地组件的描述，类型为object，其定义如下：\n",
    "\n",
    "```python\n",
    "class Tool(BaseModel):\n",
    "    type: str = \"function\"\n",
    "    function: Function = Field(..., description=\"工具信息\")\n",
    "\n",
    "class Function(BaseModel):\n",
    "    name: str = Field(..., description=\"工具名称\")\n",
    "    description: str = Field(..., description=\"工具描述\")\n",
    "    parameters: dict = Field(..., description=\"工具参数, json_schema格式\")\n",
    "```\n",
    "\n",
    "`ToolOutput`是本地组件的执行结果，需要再次上传到Agent，参与思考，类型为object，其定义如下：\n",
    "```python\n",
    "class ToolOutput(BaseModel):\n",
    "    tool_call_id: str = Field(..., description=\"工具调用ID\")\n",
    "    output: str = Field(..., description=\"工具输出\")\n",
    "\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 4、ToolCall的第一个例子\n",
    "\n",
    "我们继续以上文中提到的查找张三生日为例，看一下完整的流程是怎么样的\n",
    "\n",
    "### 前置工作，在AppBuilder平台上创建一个白板应用（可以跳过）\n",
    "\n",
    "网页链接：https://appbuilder.cloud.baidu.com/\n",
    "\n",
    "注册后，进入控制台：https://console.bce.baidu.com/ai_apaas/dialogHome\n",
    "\n",
    "点击左上角的【创建应用】-> 【AI自动配置】，我们输入以下Prompt，自动生成Agent：`你是智能问题解决者，自动集成多种工具组件，解决用户各类问题`\n",
    "\n",
    "<img src=\"https://chengmo-dev1.bj.bcebos.com/page8.png\" alt=\"drawing\" width=\"1000\"/>\n",
    "\n",
    "最终生成的Agent长这个样子：\n",
    "\n",
    "<img src=\"https://chengmo-dev1.bj.bcebos.com/page9.png\" alt=\"drawing\" width=\"1000\"/>\n",
    "\n",
    "而后点击【发布】，分别在控制台的左侧【个人空间】获取`app_id`，在【我的密钥】获取`APPBUILDER_TOEN`后，就可以开始后续的操作了。\n",
    "\n",
    "当然，下面的示例代码中，我们已经提供了可以直接运行的试用Token与App，你可以直接上手运行"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import appbuilder\n",
    "\n",
    "# AppBuilder Token，替换为您个人的Token\n",
    "os.environ[\"APPBUILDER_TOKEN\"] = \"your api key\"\n",
    "\n",
    "# 应用为：智能问题解决者\n",
    "app_id = \"b9473e78-754b-463a-916b-f0a9097a8e5f\"\n",
    "app_client = appbuilder.AppBuilderClient(app_id)\n",
    "conversation_id = app_client.create_conversation()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "首次提问一个问题，应用不具备该能力，通过回答可以印证\n",
    "\n",
    "- 由于并没有关于张三同学的信息，所以Agent无法实现查询"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Agent第一次回答: 很抱歉，由于个人隐私保护的原则，我无法直接查询并告知您本公司张三同学的生日。如果您需要了解这个信息，建议您通过合法且正当的途径，比如直接询问张三同学本人，或者查阅公司内部的员工档案，但前提是您需要确保有合适的权限和授权。尊重和保护个人隐私是我们每个人的责任。\n"
     ]
    }
   ],
   "source": [
    "message_1 = app_client.run(\n",
    "    conversation_id=conversation_id,\n",
    "    query=\"请问本公司的张三同学的生日是哪天？\",\n",
    ")\n",
    "print(\"Agent第一次回答: {}\".format(message_1.content.answer))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**output**\n",
    "```\n",
    "Agent第一次回答: 为了回答这个问题，我们首先需要明确几个关键点：\n",
    "\n",
    "1. **问题理解**：\n",
    "   - 需要确定的是“张三同学的生日”。\n",
    "\n",
    "2. **工具选择**：\n",
    "   - 由于问题涉及的是特定个人的信息（张三的生日），这通常不是通过工具或系统查询能得到的，而是需要通过公司内部的人事记录或直接询问张三本人来获取。\n",
    "\n",
    "3. **解决方案生成**：\n",
    "   - **步骤一**：首先，尝试访问公司的人事系统或员工档案，看是否有张三的生日信息记录。\n",
    "   - **步骤二**：如果人事系统或员工档案中没有相关信息，或者你不具备访问权限，那么可以考虑直接询问张三本人或其同事，看是否有人知道他的生日。\n",
    "   - **步骤三**：如果以上方法都不可行，还可以尝试联系公司的人力资源部门，看他们是否能提供相关信息。\n",
    "\n",
    "4. **注意事项**：\n",
    "   - 在尝试获取张三的生日信息时，要确保遵守公司的隐私政策和相关法律法规，不要侵犯张三的隐私权。\n",
    "   - 如果张三不愿意透露他的生日信息，应尊重他的选择，并停止进一步询问。\n",
    "\n",
    "5. **可能遇到的问题**：\n",
    "   - 人事系统或员工档案中可能没有张三的生日信息。\n",
    "   - 张三或其同事可能不愿意透露生日信息。\n",
    "   - 人力资源部门可能因隐私政策而无法提供相关信息。\n",
    "\n",
    "综上所述，要确定张三的生日，最直接且尊重隐私的方法是直接询问张三本人，或者通过公司正式渠道（如人力资源部门）在遵守隐私政策的前提下进行查询。\n",
    "```\n",
    "\n",
    "\n",
    "##### 赋予应用一个本地查询组件能力\n",
    "\n",
    "以下示例展示了三种方式来使用 ToolCall 进行调用，并演示了如何在 AppBuilder 环境中配置和执行会话调用。\n",
    "\n",
    "**方式1：使用 JSONSchema 格式直接描述 tools 调用**\n",
    "\n",
    "- 这里我们使用info_dict模拟一个数据库查询的返回结果"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_person_infomation(name: str):\n",
    "    info_dict = {\n",
    "        \"张三\": \"1980年1月1日\",\n",
    "        \"李四\": \"1975年12月31日\",\n",
    "        \"刘伟\": \"1990年12月30日\"\n",
    "    }\n",
    "\n",
    "    if name in info_dict:\n",
    "        return f\"您要查找的{name}的生日是：{info_dict[name]}\"\n",
    "    else:\n",
    "        return f\"您要查找的{name}的信息我们暂未收录，请联系管理员添加。\"\n",
    "    \n",
    "# 创建工具的描述：json_schema格式\n",
    "tools = [\n",
    "    {\n",
    "        \"type\": \"function\",\n",
    "        \"function\": {\n",
    "            \"name\": \"get_person_infomation\",\n",
    "            \"description\": \"查找公司内指定人员的信息\",\n",
    "            \"parameters\": {\n",
    "                \"type\": \"object\",\n",
    "                \"properties\": {\n",
    "                    \"name\": {\n",
    "                        \"type\": \"string\",\n",
    "                        \"description\": \"人员名称，例如：张三、李四\",\n",
    "                    },\n",
    "                },\n",
    "                \"required\": [\"name\"],\n",
    "            },\n",
    "        },\n",
    "    }\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 现在我们已经完成了本地tool组件的设计，接下来我们将tool的功能赋予Client应用"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Agent的中间思考过程：\n",
      "{\n",
      "    \"code\": 0,\n",
      "    \"message\": \"\",\n",
      "    \"status\": \"interrupt\",\n",
      "    \"event_type\": \"Interrupt\",\n",
      "    \"content_type\": \"contexts\",\n",
      "    \"detail\": {\n",
      "        \"text\": {\n",
      "            \"function_call\": {\n",
      "                \"thought\": \"用户想要查询公司内指定人员张三的生日信息，这是一个具有明确目的和关键信息的需求。根据我们可用的工具，get_person_infomation 工具能够查找公司内指定人员的信息，包括生日等。因此，通过调用这个工具并传入张三作为参数，我们可以获取到张三的生日信息，从而满足用户的需求。\",\n",
      "                \"name\": \"get_person_infomation\",\n",
      "                \"arguments\": {\n",
      "                    \"name\": \"张三\"\n",
      "                },\n",
      "                \"usage\": {\n",
      "                    \"prompt_tokens\": 564,\n",
      "                    \"completion_tokens\": 115,\n",
      "                    \"total_tokens\": 679,\n",
      "                    \"name\": \"ERNIE-4.0-8K\",\n",
      "                    \"type\": \"plan\"\n",
      "                },\n",
      "                \"tool_call_id\": \"baf86c61-6627-4229-bc81-a17eda1bce36\"\n",
      "            },\n",
      "            \"used_tool\": []\n",
      "        }\n",
      "    },\n",
      "    \"usage\": null,\n",
      "    \"tool_calls\": [\n",
      "        {\n",
      "            \"id\": \"baf86c61-6627-4229-bc81-a17eda1bce36\",\n",
      "            \"type\": \"function\",\n",
      "            \"function\": {\n",
      "                \"name\": \"get_person_infomation\",\n",
      "                \"arguments\": {\n",
      "                    \"name\": \"张三\"\n",
      "                }\n",
      "            }\n",
      "        }\n",
      "    ]\n",
      "}\n",
      "Agent思考结束，等待我们上传本地结果\n",
      "\n"
     ]
    }
   ],
   "source": [
    "message_2 = app_client.run(\n",
    "    conversation_id=conversation_id,\n",
    "    query=\"请问本公司的张三同学的生日是哪天？\",\n",
    "    tools=tools\n",
    ")\n",
    "print(\"Agent的中间思考过程：\")\n",
    "print(message_2.content.events[-1].model_dump_json(indent=4))\n",
    "print(\"Agent思考结束，等待我们上传本地结果\\n\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**output**\n",
    "这部分输出为Client应用的思考过程\n",
    "```\n",
    "Agent的中间思考过程：\n",
    "{\n",
    "    \"code\": 0,\n",
    "    \"message\": \"\",\n",
    "    \"status\": \"interrupt\",\n",
    "    \"event_type\": \"Interrupt\",\n",
    "    \"content_type\": \"contexts\",\n",
    "    \"detail\": {\n",
    "        \"text\": {\n",
    "            \"function_call\": {\n",
    "                \"thought\": \"用户想要查询公司内张三同学的生日信息，这个需求很明确，且背景信息也足够。我可以使用get_person_infomation工具来查找张三的生日信息。\",\n",
    "                \"name\": \"get_person_infomation\",\n",
    "                \"arguments\": {\n",
    "                    \"name\": \"张三\"\n",
    "                },\n",
    "                \"usage\": {\n",
    "                    \"prompt_tokens\": 697,\n",
    "                    \"completion_tokens\": 87,\n",
    "                    \"total_tokens\": 784,\n",
    "                    \"name\": \"ERNIE-4.0-Turbo-8K\",\n",
    "                    \"type\": \"plan\"\n",
    "                },\n",
    "                \"tool_call_id\": \"c23309f7-e24a-4476-85e2-3ef9cfd4f6ed\"\n",
    "            },\n",
    "            \"used_tool\": []\n",
    "...\n",
    "    ]\n",
    "}\n",
    "Agent思考结束，等待我们上传本地结果\n",
    "```\n",
    "\n",
    "- 大模型下发了调用本地函数的参数，我们使用这个参数调用本地函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "local_func_result: 您要查找的张三的生日是：1980年1月1日\n",
      "\n"
     ]
    }
   ],
   "source": [
    "tool_call = message_2.content.events[-1].tool_calls[-1]\n",
    "tool_call_id = tool_call.id\n",
    "tool_call_argument = tool_call.function.arguments\n",
    "local_func_result = get_person_infomation(**tool_call_argument)\n",
    "print(\"local_func_result: {}\\n\".format(local_func_result))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**output**\n",
    "```\n",
    "local_func_result: 您要查找的张三的生日是：1980年1月1日\n",
    "```\n",
    "\n",
    "- 向应用返回本地运行的结果，完成本地函数toolcall调用"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Agent 拥有了本地函数调用能力后，回答是: 您要找的张三的生日是1980年1月1日。\n"
     ]
    }
   ],
   "source": [
    "message_3 = app_client.run(\n",
    "    conversation_id=conversation_id,\n",
    "    tool_outputs=[{\n",
    "        \"tool_call_id\": tool_call_id,\n",
    "        \"output\": local_func_result\n",
    "    }]\n",
    ")\n",
    "print(\"Agent 拥有了本地函数调用能力后，回答是: {}\".format(message_3.content.answer))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**output**\n",
    "```\n",
    "Agent的中间思考过程：\n",
    "{\n",
    "    \"code\": 0,\n",
    "    \"message\": \"\",\n",
    "    \"status\": \"interrupt\",\n",
    "    \"event_type\": \"Interrupt\",\n",
    "    \"content_type\": \"contexts\",\n",
    "    \"detail\": {\n",
    "        \"text\": {\n",
    "            \"function_call\": {\n",
    "                \"thought\": \"用户想要查询公司内张三同学的生日信息，这个需求很明确，且背景信息也足够。我可以使用get_person_infomation工具来查找张三的生日信息。\",\n",
    "                \"name\": \"get_person_infomation\",\n",
    "                \"arguments\": {\n",
    "                    \"name\": \"张三\"\n",
    "                },\n",
    "                \"usage\": {\n",
    "                    \"prompt_tokens\": 697,\n",
    "                    \"completion_tokens\": 87,\n",
    "                    \"total_tokens\": 784,\n",
    "                    \"name\": \"ERNIE-4.0-Turbo-8K\",\n",
    "                    \"type\": \"plan\"\n",
    "                },\n",
    "                \"tool_call_id\": \"c23309f7-e24a-4476-85e2-3ef9cfd4f6ed\"\n",
    "            },\n",
    "            \"used_tool\": []\n",
    "...\n",
    "    ]\n",
    "}\n",
    "Agent思考结束，等待我们上传本地结果\n",
    "\n",
    "Output is truncated. View as a scrollable element or open in a text editor. Adjust cell output settings...\n",
    "\n",
    "local_func_result: 您要查找的张三的生日是：1980年1月1日\n",
    "\n",
    "Agent 拥有了本地函数调用能力后，回答是: # 解决方案\n",
    "\n",
    "## 问题分析\n",
    "\n",
    "用户想要查询公司内张三同学的生日信息。这是一个明确且具体的需求，我们可以通过`get_person_infomation`工具来获取这一信息。\n",
    "\n",
    "## 工具运用\n",
    "\n",
    "1. **工具选择**：`get_person_infomation`\n",
    "2. **参数设置**：\n",
    "\n",
    "\t* `name`：张三\n",
    "\n",
    "3. **执行结果**：张三的生日是1980年1月1日。\n",
    "\n",
    "## 解决方案步骤\n",
    "\n",
    "1. 使用`get_person_infomation`工具，并设置参数`name`为“张三”。\n",
    "2. 等待工具执行，并获取张三的生日信息。\n",
    "3. 将获取到的生日信息（1980年1月1日）告知用户。\n",
    "\n",
    "## 注意事项\n",
    "\n",
    "* 确保在使用`get_person_infomation`工具时，输入的姓名与公司内部记录的姓名完全一致，以避免查询错误。\n",
    "* 如果工具返回“未找到”或类似结果，请检查姓名是否有误或联系公司人事部门确认信息。\n",
    "\n",
    "通过上述步骤，我们可以准确地回答用户的问题，并提供张三的生日信息。\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**方式2：使用 function_to_model 将函数对象传递为 ToolCall 的调用**\n",
    "\n",
    "- 前置步骤：设置环境变量和初始化操作"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import appbuilder\n",
    "import os\n",
    "import json\n",
    "\n",
    "# 请前往千帆AppBuilder官网创建密钥，流程详见：https://cloud.baidu.com/doc/AppBuilder/s/Olq6grrt6#1%E3%80%81%E5%88%9B%E5%BB%BA%E5%AF%86%E9%92%A5\n",
    "# 设置环境变量\n",
    "# AppBuilder Token，替换为您个人的Token\n",
    "os.environ[\"APPBUILDER_TOKEN\"] = \"your api key\"\n",
    "\n",
    "# 应用为：智能问题解决者\n",
    "app_id = \"b9473e78-754b-463a-916b-f0a9097a8e5f\"\n",
    "# 初始化智能体\n",
    "client = appbuilder.AppBuilderClient(app_id)\n",
    "# 创建会话\n",
    "conversation_id = client.create_conversation()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 定义函数和函数列表，按照谷歌规范写好注释"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "#定义示例函数\n",
    "def get_current_weather(location: str, unit: str) -> str:\n",
    "  \"\"\"获取指定中国城市的当前天气信息。\n",
    "\n",
    "  仅支持中国城市的天气查询。参数 `location` 为中国城市名称，其他国家城市不支持天气查询。\n",
    "\n",
    "  Args:\n",
    "      location (str): 城市名，例如：\"北京\"。\n",
    "      unit (int): 温度单位，支持 \"celsius\" 或 \"fahrenheit\"。\n",
    "\n",
    "  Returns:\n",
    "      str: 天气情况描述\n",
    "  \"\"\"\n",
    "  return \"北京今天25度\"\n",
    "  \n",
    "#定义函数列表\n",
    "functions = [get_current_weather]\n",
    "function_map = {f.__name__: f for f in functions}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 查看一下function_to_model函数转化的结果"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\n",
      "    \"type\": \"function\",\n",
      "    \"function\": {\n",
      "        \"name\": \"get_current_weather\",\n",
      "        \"description\": \"获取指定中国城市的当前天气信息。\\n\\n  仅支持中国城市的天气查询。参数 `location` 为中国城市名称，其他国家城市不支持天气查询。\\n\\n  Args:\\n      location (str): 城市名，例如：\\\"北京\\\"。\\n      unit (int): 温度单位，支持 \\\"celsius\\\" 或 \\\"fahrenheit\\\"。\\n\\n  Returns:\\n      str: 天气情况描述\",\n",
      "        \"parameters\": {\n",
      "            \"type\": \"object\",\n",
      "            \"properties\": {\n",
      "                \"location\": {\n",
      "                    \"name\": \"location\",\n",
      "                    \"type\": \"str\",\n",
      "                    \"description\": null,\n",
      "                    \"required\": true\n",
      "                },\n",
      "                \"unit\": {\n",
      "                    \"name\": \"unit\",\n",
      "                    \"type\": \"str\",\n",
      "                    \"description\": null,\n",
      "                    \"required\": true\n",
      "                }\n",
      "            },\n",
      "            \"required\": [\n",
      "                \"location\",\n",
      "                \"unit\"\n",
      "            ]\n",
      "        },\n",
      "        \"returns\": {\n",
      "            \"type\": \"str\",\n",
      "            \"description\": null\n",
      "        }\n",
      "    }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "print(json.dumps(appbuilder.Manifest.from_function(get_current_weather), indent=4, ensure_ascii=False))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 调用大模型进行函数调用"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "ename": "BadRequestException",
     "evalue": "request_id=928f30c6-d712-448b-a831-dc4cd15307b0 , http status code is 400, body is {\"code\": \"QuotaLimitExceeded\", \"message\": \"quota\\u8d44\\u6e90\\u5df2\\u8fbe\\u4e0a\\u9650\", \"request_id\": \"928f30c6-d712-448b-a831-dc4cd15307b0\"}",
     "output_type": "error",
     "traceback": [
      "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m",
      "\u001B[0;31mBadRequestException\u001B[0m                       Traceback (most recent call last)",
      "Cell \u001B[0;32mIn[5], line 2\u001B[0m\n\u001B[1;32m      1\u001B[0m \u001B[38;5;66;03m#调用大模型\u001B[39;00m\n\u001B[0;32m----> 2\u001B[0m msg \u001B[38;5;241m=\u001B[39m \u001B[43mclient\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mrun\u001B[49m\u001B[43m(\u001B[49m\n\u001B[1;32m      3\u001B[0m \u001B[43m  \u001B[49m\u001B[43mconversation_id\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mconversation_id\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m      4\u001B[0m \u001B[43m  \u001B[49m\u001B[43mquery\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[38;5;124;43m今天北京的天气怎么样？\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[43m,\u001B[49m\n\u001B[1;32m      5\u001B[0m \u001B[43m  \u001B[49m\u001B[43mtools\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43m \u001B[49m\u001B[43m[\u001B[49m\u001B[43mappbuilder\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mManifest\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mfrom_function\u001B[49m\u001B[43m(\u001B[49m\u001B[43mf\u001B[49m\u001B[43m)\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mmodel_dump\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43;01mfor\u001B[39;49;00m\u001B[43m \u001B[49m\u001B[43mf\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;129;43;01min\u001B[39;49;00m\u001B[43m \u001B[49m\u001B[43mfunctions\u001B[49m\u001B[43m]\u001B[49m\n\u001B[1;32m      6\u001B[0m \u001B[43m  \u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m      7\u001B[0m \u001B[38;5;28mprint\u001B[39m(msg\u001B[38;5;241m.\u001B[39mmodel_dump_json(indent\u001B[38;5;241m=\u001B[39m\u001B[38;5;241m4\u001B[39m))\n\u001B[1;32m      8\u001B[0m \u001B[38;5;66;03m# 获取最后的事件和工具调用信息\u001B[39;00m\n",
      "File \u001B[0;32m/opt/anaconda3/envs/testenv/lib/python3.9/site-packages/appbuilder/core/console/appbuilder_client/appbuilder_client.py:306\u001B[0m, in \u001B[0;36mAppBuilderClient.run\u001B[0;34m(self, conversation_id, query, file_ids, stream, tools, tool_outputs, tool_choice, end_user_id, action, **kwargs)\u001B[0m\n\u001B[1;32m    302\u001B[0m url \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mhttp_client\u001B[38;5;241m.\u001B[39mservice_url_v2(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124m/app/conversation/runs\u001B[39m\u001B[38;5;124m\"\u001B[39m)\n\u001B[1;32m    303\u001B[0m response \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mhttp_client\u001B[38;5;241m.\u001B[39msession\u001B[38;5;241m.\u001B[39mpost(\n\u001B[1;32m    304\u001B[0m     url, headers\u001B[38;5;241m=\u001B[39mheaders, json\u001B[38;5;241m=\u001B[39mreq\u001B[38;5;241m.\u001B[39mmodel_dump(), timeout\u001B[38;5;241m=\u001B[39m\u001B[38;5;28;01mNone\u001B[39;00m, stream\u001B[38;5;241m=\u001B[39m\u001B[38;5;28;01mTrue\u001B[39;00m\n\u001B[1;32m    305\u001B[0m )\n\u001B[0;32m--> 306\u001B[0m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mhttp_client\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mcheck_response_header\u001B[49m\u001B[43m(\u001B[49m\u001B[43mresponse\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m    307\u001B[0m request_id \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mhttp_client\u001B[38;5;241m.\u001B[39mresponse_request_id(response)\n\u001B[1;32m    308\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m stream:\n",
      "File \u001B[0;32m/opt/anaconda3/envs/testenv/lib/python3.9/site-packages/appbuilder/core/_client.py:120\u001B[0m, in \u001B[0;36mHTTPClient.check_response_header\u001B[0;34m(response)\u001B[0m\n\u001B[1;32m    116\u001B[0m message \u001B[38;5;241m=\u001B[39m \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mrequest_id=\u001B[39m\u001B[38;5;132;01m{}\u001B[39;00m\u001B[38;5;124m , http status code is \u001B[39m\u001B[38;5;132;01m{}\u001B[39;00m\u001B[38;5;124m, body is \u001B[39m\u001B[38;5;132;01m{}\u001B[39;00m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;241m.\u001B[39mformat(\n\u001B[1;32m    117\u001B[0m     \u001B[38;5;18m__class__\u001B[39m\u001B[38;5;241m.\u001B[39mresponse_request_id(response), status_code, response\u001B[38;5;241m.\u001B[39mtext\n\u001B[1;32m    118\u001B[0m )\n\u001B[1;32m    119\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m status_code \u001B[38;5;241m==\u001B[39m requests\u001B[38;5;241m.\u001B[39mcodes\u001B[38;5;241m.\u001B[39mbad_request:\n\u001B[0;32m--> 120\u001B[0m     \u001B[38;5;28;01mraise\u001B[39;00m BadRequestException(message)\n\u001B[1;32m    121\u001B[0m \u001B[38;5;28;01melif\u001B[39;00m status_code \u001B[38;5;241m==\u001B[39m requests\u001B[38;5;241m.\u001B[39mcodes\u001B[38;5;241m.\u001B[39mforbidden:\n\u001B[1;32m    122\u001B[0m     \u001B[38;5;28;01mraise\u001B[39;00m ForbiddenException(message)\n",
      "\u001B[0;31mBadRequestException\u001B[0m: request_id=928f30c6-d712-448b-a831-dc4cd15307b0 , http status code is 400, body is {\"code\": \"QuotaLimitExceeded\", \"message\": \"quota\\u8d44\\u6e90\\u5df2\\u8fbe\\u4e0a\\u9650\", \"request_id\": \"928f30c6-d712-448b-a831-dc4cd15307b0\"}"
     ]
    }
   ],
   "source": [
    "#调用大模型\n",
    "msg = client.run(\n",
    "  conversation_id=conversation_id,\n",
    "  query=\"今天北京的天气怎么样？\",\n",
    "  tools = [appbuilder.Manifest.from_function(f) for f in functions]\n",
    "  )\n",
    "print(msg.model_dump_json(indent=4))\n",
    "# 获取最后的事件和工具调用信息\n",
    "event = msg.content.events[-1]\n",
    "tool_call = event.tool_calls[-1]\n",
    "\n",
    "# 获取函数名称和参数\n",
    "name = tool_call.function.name\n",
    "args = tool_call.function.arguments\n",
    "\n",
    "# 将函数名称映射到具体的函数并执行\n",
    "raw_result = function_map[name](**args)\n",
    "\n",
    "# 传递工具的输出\n",
    "msg_2 = client.run(\n",
    "    conversation_id=conversation_id,\n",
    "    tool_outputs=[{\n",
    "        \"tool_call_id\": tool_call.id,\n",
    "        \"output\": str(raw_result)\n",
    "    }],\n",
    ")\n",
    "print(msg_2.model_dump_json(indent=4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**方式3: 使用装饰器进行描述**\n",
    "\n",
    "- 前置步骤：设置环境变量和初始化操作"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import json\n",
    "import appbuilder\n",
    "from appbuilder import manifest, manifest_parameter\n",
    "\n",
    "# 请前往千帆AppBuilder官网创建密钥，流程详见：https://cloud.baidu.com/doc/AppBuilder/s/Olq6grrt6#1%E3%80%81%E5%88%9B%E5%BB%BA%E5%AF%86%E9%92%A5\n",
    "# 设置环境变量\n",
    "# AppBuilder Token，替换为您个人的Token\n",
    "#os.environ[\"APPBUILDER_TOKEN\"] = \"your api key\"\n",
    "os.environ[\"APPBUILDER_TOKEN\"] = \"your api key\"\n",
    "\n",
    "# 应用为：智能问题解决者\n",
    "#app_id = \"b9473e78-754b-463a-916b-f0a9097a8e5f\"\n",
    "app_id = \"7cc4c21f-0e25-4a76-baf7-01a2b923a1a7\"\n",
    "# 初始化智能体\n",
    "client = appbuilder.AppBuilderClient(app_id)\n",
    "# 创建会话\n",
    "conversation_id = client.create_conversation()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 定义函数和函数列表，并用装饰器对函数进行进行描述."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "#使用function装饰描述函数，function_parameter装饰器描述参数，function_return装饰器描述函数返回值。\n",
    "@manifest(description=\"获取指定中国城市的当前天气信息。仅支持中国城市的天气查询。参数 `location` 为中国城市名称，其他国家城市不支持天气查询。\")\n",
    "@manifest_parameter(name=\"location\", description=\"城市名，例如：北京。\")\n",
    "@manifest_parameter(name=\"unit\", description=\"温度单位，支持 'celsius' 或 'fahrenheit'\")\n",
    "#定义示例函数\n",
    "def get_current_weather(location: str, unit: str) -> str:\n",
    "  return \"北京今天25度\"\n",
    "\n",
    "#定义函数列表\n",
    "functions = [get_current_weather]\n",
    "function_map = {f.__name__: f for f in functions}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 查看一下装饰器的转化内容"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\n",
      "    \"type\": \"function\",\n",
      "    \"function\": {\n",
      "        \"name\": \"get_current_weather\",\n",
      "        \"description\": \"获取指定中国城市的当前天气信息。仅支持中国城市的天气查询。参数 `location` 为中国城市名称，其他国家城市不支持天气查询。\",\n",
      "        \"parameters\": {\n",
      "            \"type\": \"object\",\n",
      "            \"properties\": {\n",
      "                \"location\": {\n",
      "                    \"name\": \"location\",\n",
      "                    \"type\": \"str\",\n",
      "                    \"description\": \"城市名，例如：北京。\",\n",
      "                    \"required\": true\n",
      "                },\n",
      "                \"unit\": {\n",
      "                    \"name\": \"unit\",\n",
      "                    \"type\": \"str\",\n",
      "                    \"description\": \"温度单位，支持 'celsius' 或 'fahrenheit'\",\n",
      "                    \"required\": true\n",
      "                }\n",
      "            },\n",
      "            \"required\": [\n",
      "                \"location\",\n",
      "                \"unit\"\n",
      "            ]\n",
      "        }\n",
      "    }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "# 将 model_dump() 的输出进行格式化打印\n",
    "print(\n",
    "    json.dumps(\n",
    "        appbuilder.Manifest.from_function(get_current_weather), indent=4, ensure_ascii=False\n",
    "    )\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "ename": "BadRequestException",
     "evalue": "request_id=76224253-163f-46d3-a5eb-b22c6fcec2be , http status code is 400, body is {\"code\": \"QuotaLimitExceeded\", \"message\": \"quota\\u8d44\\u6e90\\u5df2\\u8fbe\\u4e0a\\u9650\", \"request_id\": \"76224253-163f-46d3-a5eb-b22c6fcec2be\"}",
     "output_type": "error",
     "traceback": [
      "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m",
      "\u001B[0;31mBadRequestException\u001B[0m                       Traceback (most recent call last)",
      "Cell \u001B[0;32mIn[10], line 2\u001B[0m\n\u001B[1;32m      1\u001B[0m \u001B[38;5;66;03m#调用大模型\u001B[39;00m\n\u001B[0;32m----> 2\u001B[0m msg \u001B[38;5;241m=\u001B[39m \u001B[43mclient\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mrun\u001B[49m\u001B[43m(\u001B[49m\n\u001B[1;32m      3\u001B[0m \u001B[43m  \u001B[49m\u001B[43mconversation_id\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mconversation_id\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m      4\u001B[0m \u001B[43m  \u001B[49m\u001B[43mquery\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[38;5;124;43m今天北京的天气怎么样？\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[43m,\u001B[49m\n\u001B[1;32m      5\u001B[0m \u001B[43m  \u001B[49m\u001B[43mtools\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43m \u001B[49m\u001B[43m[\u001B[49m\u001B[43mget_current_weather\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m__ab_manifest__\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mmodel_dump\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\u001B[43m]\u001B[49m\n\u001B[1;32m      6\u001B[0m \u001B[43m  \u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m      7\u001B[0m \u001B[38;5;28mprint\u001B[39m(msg\u001B[38;5;241m.\u001B[39mmodel_dump_json(indent\u001B[38;5;241m=\u001B[39m\u001B[38;5;241m4\u001B[39m))\n\u001B[1;32m      8\u001B[0m \u001B[38;5;66;03m# 获取最后的事件和工具调用信息\u001B[39;00m\n",
      "File \u001B[0;32m/opt/anaconda3/envs/testenv/lib/python3.9/site-packages/appbuilder/core/console/appbuilder_client/appbuilder_client.py:306\u001B[0m, in \u001B[0;36mAppBuilderClient.run\u001B[0;34m(self, conversation_id, query, file_ids, stream, tools, tool_outputs, tool_choice, end_user_id, action, **kwargs)\u001B[0m\n\u001B[1;32m    302\u001B[0m url \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mhttp_client\u001B[38;5;241m.\u001B[39mservice_url_v2(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124m/app/conversation/runs\u001B[39m\u001B[38;5;124m\"\u001B[39m)\n\u001B[1;32m    303\u001B[0m response \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mhttp_client\u001B[38;5;241m.\u001B[39msession\u001B[38;5;241m.\u001B[39mpost(\n\u001B[1;32m    304\u001B[0m     url, headers\u001B[38;5;241m=\u001B[39mheaders, json\u001B[38;5;241m=\u001B[39mreq\u001B[38;5;241m.\u001B[39mmodel_dump(), timeout\u001B[38;5;241m=\u001B[39m\u001B[38;5;28;01mNone\u001B[39;00m, stream\u001B[38;5;241m=\u001B[39m\u001B[38;5;28;01mTrue\u001B[39;00m\n\u001B[1;32m    305\u001B[0m )\n\u001B[0;32m--> 306\u001B[0m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mhttp_client\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mcheck_response_header\u001B[49m\u001B[43m(\u001B[49m\u001B[43mresponse\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m    307\u001B[0m request_id \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mhttp_client\u001B[38;5;241m.\u001B[39mresponse_request_id(response)\n\u001B[1;32m    308\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m stream:\n",
      "File \u001B[0;32m/opt/anaconda3/envs/testenv/lib/python3.9/site-packages/appbuilder/core/_client.py:120\u001B[0m, in \u001B[0;36mHTTPClient.check_response_header\u001B[0;34m(response)\u001B[0m\n\u001B[1;32m    116\u001B[0m message \u001B[38;5;241m=\u001B[39m \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mrequest_id=\u001B[39m\u001B[38;5;132;01m{}\u001B[39;00m\u001B[38;5;124m , http status code is \u001B[39m\u001B[38;5;132;01m{}\u001B[39;00m\u001B[38;5;124m, body is \u001B[39m\u001B[38;5;132;01m{}\u001B[39;00m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;241m.\u001B[39mformat(\n\u001B[1;32m    117\u001B[0m     \u001B[38;5;18m__class__\u001B[39m\u001B[38;5;241m.\u001B[39mresponse_request_id(response), status_code, response\u001B[38;5;241m.\u001B[39mtext\n\u001B[1;32m    118\u001B[0m )\n\u001B[1;32m    119\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m status_code \u001B[38;5;241m==\u001B[39m requests\u001B[38;5;241m.\u001B[39mcodes\u001B[38;5;241m.\u001B[39mbad_request:\n\u001B[0;32m--> 120\u001B[0m     \u001B[38;5;28;01mraise\u001B[39;00m BadRequestException(message)\n\u001B[1;32m    121\u001B[0m \u001B[38;5;28;01melif\u001B[39;00m status_code \u001B[38;5;241m==\u001B[39m requests\u001B[38;5;241m.\u001B[39mcodes\u001B[38;5;241m.\u001B[39mforbidden:\n\u001B[1;32m    122\u001B[0m     \u001B[38;5;28;01mraise\u001B[39;00m ForbiddenException(message)\n",
      "\u001B[0;31mBadRequestException\u001B[0m: request_id=76224253-163f-46d3-a5eb-b22c6fcec2be , http status code is 400, body is {\"code\": \"QuotaLimitExceeded\", \"message\": \"quota\\u8d44\\u6e90\\u5df2\\u8fbe\\u4e0a\\u9650\", \"request_id\": \"76224253-163f-46d3-a5eb-b22c6fcec2be\"}"
     ]
    }
   ],
   "source": [
    "# 调用大模型\n",
    "msg = client.run(\n",
    "    conversation_id=conversation_id,\n",
    "    query=\"今天北京的天气怎么样？\",\n",
    "    tools=[appbuilder.Manifest.from_function(get_current_weather)],\n",
    ")\n",
    "print(msg.model_dump_json(indent=4))\n",
    "# 获取最后的事件和工具调用信息\n",
    "event = msg.content.events[-1]\n",
    "tool_call = event.tool_calls[-1]\n",
    "\n",
    "# 获取函数名称和参数\n",
    "name = tool_call.function.name\n",
    "args = tool_call.function.arguments\n",
    "\n",
    "# 将函数名称映射到具体的函数并执行\n",
    "raw_result = function_map[name](**args)\n",
    "\n",
    "# 传递工具的输出\n",
    "msg_2 = client.run(\n",
    "    conversation_id=conversation_id,\n",
    "    tool_outputs=[{\n",
    "        \"tool_call_id\": tool_call.id,\n",
    "        \"output\": str(raw_result)\n",
    "    }],\n",
    ")\n",
    "print(msg_2.model_dump_json(indent=4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 5、ToolCal第二个例子-调用本地工具并且代码更简洁\n",
    "\n",
    "我们可以使用AppBuilderClient应用来执行tool_call操作，完成指定的命令，但是需要自己配置client的思考与运行流程，较为繁琐。SDK提供了使用AppBuilderEventHandler简化tool_call操作的功能\n",
    "\n",
    "##### 配置运行环境&导入Client应用"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import appbuilder\n",
    "\n",
    "\n",
    "# AppBuilder Token，替换为您个人的Token\n",
    "os.environ[\"APPBUILDER_TOKEN\"] = \"your api key\"\n",
    "\n",
    "# 应用为：智能问题解决者\n",
    "app_id = \"b9473e78-754b-463a-916b-f0a9097a8e5f\"\n",
    "app_client = appbuilder.AppBuilderClient(app_id)\n",
    "conversation_id = app_client.create_conversation()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### 继承AppBuilderEventHandler类，并实现针对各类型event的处理方法"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from appbuilder.core.console.appbuilder_client.event_handler import AppBuilderEventHandler\n",
    "class MyEventHandler(AppBuilderEventHandler):\n",
    "    def execute_local_command(self, cmd: str):\n",
    "        import subprocess\n",
    "        try:\n",
    "            result = subprocess.check_output(cmd, shell=True).decode(\"utf-8\")\n",
    "            if result.strip() == \"\":\n",
    "                return \"命令执行成功，无返回值\"\n",
    "            return result\n",
    "        except Exception as e:\n",
    "            return str(e)\n",
    "    \n",
    "    def interrupt(self, run_context, run_response):\n",
    "        thought = run_context.current_thought\n",
    "        # 绿色打印\n",
    "        print(\"\\033[1;32m\", \"-> Agent 中间思考: \", thought, \"\\033[0m\")\n",
    "\n",
    "        tool_output = []\n",
    "        for tool_call in run_context.current_tool_calls:\n",
    "            tool_call_id = tool_call.id\n",
    "            tool_res = self.execute_local_command(\n",
    "                **tool_call.function.arguments)\n",
    "            # 蓝色打印\n",
    "            print(\"\\033[1;34m\", \"-> 本地ToolCall结果: \\n\", tool_res, \"\\033[0m\\n\")\n",
    "            tool_output.append(\n",
    "                {\n",
    "                    \"tool_call_id\": tool_call_id,\n",
    "                    \"output\": tool_res\n",
    "                }\n",
    "            )\n",
    "        return tool_output\n",
    "    \n",
    "    def success(self, run_context, run_response):\n",
    "        print(\"\\n\\033[1;31m\",\"-> Agent 非流式回答: \\n\", run_response.answer, \"\\033[0m\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### 定义本地的tools工具\n",
    "\n",
    "通过`subprocess.check_output`方法，可以在终端中执行命令，并返回执行结果"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tools = [\n",
    "    {\n",
    "        \"type\": \"function\",\n",
    "        \"function\": {\n",
    "            \"name\": \"execute_local_command\",\n",
    "            \"description\": \"可以在bash环境中，执行输入的指令，注意，一次只能执行一个原子命令。例如：ls\",\n",
    "            \"parameters\": {\n",
    "                \"type\": \"object\",\n",
    "                \"properties\": {\n",
    "                    \"cmd\": {\n",
    "                        \"type\": \"string\",\n",
    "                        \"description\": \"需要执行的指令\",\n",
    "                    },\n",
    "                },\n",
    "                \"required\": [\"cmd\"],\n",
    "            },\n",
    "        },\n",
    "    }\n",
    "]\n",
    "\n",
    "with app_client.run_with_handler(\n",
    "        conversation_id = conversation_id,\n",
    "        query = \"请问当前文件夹下有哪些文件？如果没有test.txt文件，请新建一个test.txt文件，内容为：Hello World！\",\n",
    "        tools = tools,\n",
    "        event_handler = MyEventHandler(),\n",
    "    ) as run:\n",
    "        run.until_done()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**output**\n",
    "```\n",
    " -> Agent 中间思考:  首先，我需要使用execute_local_command工具来执行'ls'命令，列出当前文件夹下的所有文件。然后，我需要检查输出中是否存在test.txt文件。如果不存在，我将再次使用execute_local_command工具来执行'echo \"Hello World\" > test.txt'命令，以创建并写入test.txt文件。 \n",
    " -> 本地ToolCall结果: \n",
    " multi_tool_call.ipynb\n",
    "multi_tool_call.py\n",
    "multi_tool_call_with_handler.ipynb\n",
    "multi_tool_call_with_handler.py\n",
    "sdk_ knowledgebase.ipynb\n",
    "sdk_trace.ipynb\n",
    "simple_tool_call.ipynb\n",
    "simple_tool_call.py\n",
    "tmp.log\n",
    "黑神话(悟空).pdf\n",
    " \n",
    "\n",
    " -> Agent 中间思考:  根据execute_local_command工具的返回结果，当前文件夹下并没有test.txt文件。因此，我需要使用execute_local_command工具来执行'echo \"Hello World\" > test.txt'命令，以创建并写入test.txt文件。 \n",
    " -> 本地ToolCall结果: \n",
    " 命令执行成功，无返回值 \n",
    "\n",
    "\n",
    " -> Agent 非流式回答: \n",
    " 当前文件夹下的文件包括：\n",
    "\n",
    "- multi_tool_call.ipynb\n",
    "- multi_tool_call.py\n",
    "- multi_tool_call_with_handler.ipynb\n",
    "...\n",
    "- tmp.log\n",
    "- 黑神话(悟空).pdf\n",
    "\n",
    "经过检查，发现当前文件夹下**不存在**test.txt文件。因此，已经为您新建了一个test.txt文件，并写入了内容“Hello World！”。 \n",
    "```\n",
    "\n",
    "- 使用AppBuilderEventHandler架构可以简化client的交互方式"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 6 使用异步调用优化toolcall并发执行效率\n",
    "SDK提供了异步调用接口，可以大幅提升并发执行效率。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Copyright (c) 2024 Baidu, Inc. All Rights Reserved.\n",
    "#\n",
    "# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
    "# you may not use this file except in compliance with the License.\n",
    "# You may obtain a copy of the License at\n",
    "#\n",
    "#     http://www.apache.org/licenses/LICENSE-2.0\n",
    "#\n",
    "# Unless required by applicable law or agreed to in writing, software\n",
    "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
    "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
    "# See the License for the specific language governing permissions and\n",
    "# limitations under the License.\n",
    "\n",
    "import appbuilder\n",
    "import asyncio\n",
    "from appbuilder.core.console.appbuilder_client.async_event_handler import (\n",
    "    AsyncAppBuilderEventHandler,\n",
    ")\n",
    "\n",
    "\n",
    "class MyEventHandler(AsyncAppBuilderEventHandler):\n",
    "    def get_current_weather(self, location=None, unit=\"摄氏度\"):\n",
    "        return \"{} 的温度是 {} {}\".format(location, 20, unit)\n",
    "\n",
    "    async def interrupt(self, run_context, run_response):\n",
    "        thought = run_context.current_thought\n",
    "        # 绿色打印\n",
    "        print(\"\\033[1;32m\", \"-> Agent 中间思考: \", thought, \"\\033[0m\")\n",
    "\n",
    "        tool_output = []\n",
    "        for tool_call in run_context.current_tool_calls:\n",
    "            tool_call_id = tool_call.id\n",
    "            tool_res = self.get_current_weather(**tool_call.function.arguments)\n",
    "            # 蓝色打印\n",
    "            print(\"\\033[1;34m\", \"-> 本地ToolCallId: \", tool_call_id, \"\\033[0m\")\n",
    "            print(\"\\033[1;34m\", \"-> ToolCall结果: \", tool_res, \"\\033[0m\\n\")\n",
    "            tool_output.append({\"tool_call_id\": tool_call_id, \"output\": tool_res})\n",
    "        return tool_output\n",
    "\n",
    "    async def success(self, run_context, run_response):\n",
    "        print(\"\\n\\033[1;31m\", \"-> Agent 非流式回答: \", run_response.answer, \"\\033[0m\")\n",
    "\n",
    "\n",
    "def main():\n",
    "    app_id = \"b2a972c5-e082-46e5-b313-acbf51792422\"\n",
    "    tools = [\n",
    "        {\n",
    "            \"type\": \"function\",\n",
    "            \"function\": {\n",
    "                \"name\": \"get_current_weather\",\n",
    "                \"description\": \"仅支持中国城市的天气查询，参数location为中国城市名称，其他国家城市不支持天气查询\",\n",
    "                \"parameters\": {\n",
    "                    \"type\": \"object\",\n",
    "                    \"properties\": {\n",
    "                        \"location\": {\n",
    "                            \"type\": \"string\",\n",
    "                            \"description\": \"城市名，举例：北京\",\n",
    "                        },\n",
    "                        \"unit\": {\"type\": \"string\", \"enum\": [\"摄氏度\", \"华氏度\"]},\n",
    "                    },\n",
    "                    \"required\": [\"location\", \"unit\"],\n",
    "                },\n",
    "            },\n",
    "        }\n",
    "    ]\n",
    "\n",
    "    appbuilder.logger.setLoglevel(\"ERROR\")\n",
    "\n",
    "    async def agent_run(client, query):\n",
    "        conversation_id = await client.create_conversation()\n",
    "        with await client.run_with_handler(\n",
    "            conversation_id=conversation_id,\n",
    "            query=query,\n",
    "            tools=tools,\n",
    "            event_handler=MyEventHandler(),\n",
    "        ) as run:\n",
    "            await run.until_done()\n",
    "\n",
    "    async def agent_handle():\n",
    "        client = appbuilder.AsyncAppBuilderClient(app_id)\n",
    "        task1 = asyncio.create_task(agent_run(client, \"北京的天气怎么样\"))\n",
    "        task2 = asyncio.create_task(agent_run(client, \"上海的天气怎么样\"))\n",
    "        await asyncio.gather(task1, task2)\n",
    "        await client.http_client.session.close()\n",
    "\n",
    "    loop = asyncio.get_event_loop()\n",
    "    loop.run_until_complete(agent_handle())\n",
    "\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    main()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 7、项目总结\n",
    "\n",
    "本项目通过多个知识点的学习，以及两个使用AppBuilder-SDK的实操，最终完成了一个支持ToolCall AIAgent的构建。\n",
    "\n",
    "- 理论学习：了解AIAgent的基础知识\n",
    "- 上手实操：深入了解Agent中的FunctionCall运行流程\n",
    "- 上手实操：入门百度智能云千帆AppBuilder，在十分钟内打造一个个性化AIAgent\n",
    "- 上手实操：使用AppBuilder-SDK打造一个端云组件联动的进阶Agent\n",
    "\n",
    "\n",
    "希望您可以不吝`Star`，给`AppBuilder-SDK`一些鼓励，期待您的`PR`，一起共建AIAgent生态。\n",
    "\n",
    "Github地址：https://github.com/baidubce/app-builder\n",
    "\n",
    "<img src=\"https://chengmo-dev1.bj.bcebos.com/page10.png\" alt=\"drawing\" width=\"1000\"/>\n",
    "\n",
    "最后，您也可以进入`AppBuilder-SDK`的WX交流群，和大家一起交流AppBuilder使用及开发心得。\n",
    "\n",
    "<img src=\"https://chengmo-dev1.bj.bcebos.com/wechat_group.png\" alt=\"drawing\" width=\"1000\"/>"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "testenv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.20"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
